﻿using System;
using System.Collections;
using System.Collections.Generic;

namespace Lhz.Test.Common
{
    public static class ObjectComparer
    {
        public static bool AreSame(this object item, object target)
        {
            return AreSame(item, target, null, null);
        }

        private static bool AreSame(object a, object b, List<object> comparedItemsA, List<object> comparedItemsB)
        {
            if (comparedItemsA == null)
            {
                comparedItemsA = new List<object>();
            }
            if (comparedItemsB == null)
            {
                comparedItemsB = new List<object>();
            }
            if (a == null || b == null)
            {
                if (a != null || b != null)
                {
                    return false;
                }
                else
                {
                    return true;
                }
            }
            var typeA = a.GetType();
            var typeB = b.GetType();
            if (typeA.IsValueType || typeA == typeof(string))
            {
                return a.Equals(b);
            }
            else if (typeof(IEnumerable).IsAssignableFrom(typeA))
            {
                var listA = new List<object>();
                var listB = new List<object>();
                foreach (var itemA in a as IEnumerable)
                {
                    listA.Add(itemA);
                }
                foreach (var itemB in b as IEnumerable)
                {
                    listB.Add(itemB);
                }
                if (listA.Count != listB.Count)
                {
                    return false;
                }
                var usedA = new List<object>();
                var usedB = new List<object>();
                foreach (var itemA in listA)
                {
                    var hasSameOne = false;
                    foreach (var itemB in listB)
                    {
                        if (!usedA.Contains(itemA) && !usedB.Contains(itemB) && AreSame(itemA, itemB, comparedItemsA, comparedItemsB))
                        {
                            hasSameOne = true;
                            usedA.Add(itemA);
                            usedB.Add(itemB);
                            break;
                        }
                    }
                    if (!hasSameOne)
                    {
                        return false;
                    }
                }
                return true;
            }
            else if (typeA.IsClass)
            {
                if (comparedItemsA.Contains(a) && comparedItemsB.Contains(b))
                {
                    return true;
                }
                comparedItemsA.Add(a);
                comparedItemsB.Add(b);
                foreach (var piA in typeA.GetProperties())
                {
                    var valueA = piA.GetValue(a);
                    var piB = typeB.GetProperty(piA.Name);
                    if (piB == null)
                    {
                        return false;
                    }
                    var valueB = piB.GetValue(b);
                    if (typeof(IEnumerable).IsAssignableFrom(piA.PropertyType) && typeof(IEnumerable).IsAssignableFrom(piB.PropertyType))
                    {
                        if ((valueA == null || !(valueA as IEnumerable).GetEnumerator().MoveNext()) && (valueB == null || !(valueB as IEnumerable).GetEnumerator().MoveNext()))
                        {
                            return true;
                        }
                    }
                    if (!AreSame(valueA, valueB, comparedItemsA, comparedItemsB))
                    {
                        return false;
                    }
                }
                return true;
            }
            else
            {
                throw new NotSupportedException();
            }
        }
    }
}
